package ru.myrtle.umf.tools.ugen;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;

import ru.myrtle.umf.codegen.java.JavaImportBuilder;
import ru.myrtle.umf.UAttribute;
import ru.myrtle.umf.UClass;
import ru.myrtle.umf.UClassifier;
import ru.myrtle.umf.UDataType;
import ru.myrtle.umf.UEnumeration;
import ru.myrtle.umf.UEnumerationLiteral;
import ru.myrtle.umf.UFeature;
import ru.myrtle.umf.UModel;
import ru.myrtle.umf.UOperation;
import ru.myrtle.umf.UPackage;
import ru.myrtle.umf.UParameter;
import ru.myrtle.umf.UReference;

public final class UGenUtil {

    private static final String EMPTY_STRING = "";

    /** Valid hexadecimal digits */
    private static final String HEXADECIMAL_DIGITS = "0123456789abcdefABCDEF";

    private static final char DOT = '.';

    private static final String CNV_CHARACTER = "((Character) %s).charValue()";
    private static final String CNV_BOOLEAN = "((Boolean) %s).booleanValue()";
    private static final String CNV_BYTE = "((Byte) %s).byteValue()";
    private static final String CNV_SHORT = "((Short) %s).shortValue()";
    private static final String CNV_INTEGER = "((Integer) %s).intValue()";
    private static final String CNV_LONG = "((Long) %s).longValue()";
    private static final String CNV_FLOAT = "((Float) %s).floatValue()";
    private static final String CNV_DOUBLE = "((Double) %s).doubleValue()";
    private static final String CNV_OBJECT = "(%s) %s";

    private static final String DEFAULT_PACKAGE_NAME = "defaultPackage";

    private static final String SUFFIX_IMPL = ".impl";
    private static final String SUFFIX_CONVERTER = ".converter";

    private static final String DEFAULT_LITERAL_OBJECT = "null";
    private static final String DEFAULT_LITERAL_BOOLEAN = "Boolean.FALSE";
    private static final String DEFAULT_LITERAL_INTEGER = "new Integer(0)";
    private static final String DEFAULT_LITERAL_FLOAT = "new Float(0.0F)";
    private static final String DEFAULT_LITERAL_DOUBLE = "new Double(0.0)";
    private static final String DEFAULT_LITERAL_LONG = "new Long(0)";
    private static final String DEFAULT_LITERAL_SHORT = "new Short((short) 0)";
    private static final String DEFAULT_LITERAL_BYTE = "new Byte((byte) 0)";
    private static final String DEFAULT_LITERAL_CHARACTER = "new Character('\\u0000')";

    /*
     *
     */

    private static class CompositeName {

        public final String namespace;
        public final String name;

        public CompositeName(String namespace, String name) {
            this.namespace = namespace;
            this.name = name;
        }

        public static CompositeName build(String source) {
            int index = source.lastIndexOf(DOT);

            if (index < 0) {
                return new CompositeName(
                        "", source
                );
            }
            else {
                return new CompositeName(
                        source.substring(
                                0, index
                        ),
                        source.substring(
                                index + 1
                        )
                );
            }
        }
    }

    /*
     *
     */

    public static String capName(String name) {
        if (name == null) {
            return null;
        }

        if (name.length() > 0) {
            return Character.toUpperCase(
                    name.charAt(0)
            ) + (name.length() > 1 ? name.substring(1) : "");
        }
        else {
            return name;
        }
    }

    public static String uncapName(String name) {
        if (name == null) {
            return null;
        }

        if (name.length() > 0) {
            return Character.toLowerCase(
                    name.charAt(0)
            ) + (name.length() > 1 ? name.substring(1) : "");
        }
        else {
            return name;
        }
    }

    public static String toUnderLineFormat(String name) {
        if (name != null && name.length() > 0) {
            StringBuffer buffer = new StringBuffer();

            char oldChar = name.charAt(0);

            buffer.append(
                    Character.toUpperCase(oldChar)
            );

            for (int i = 1; i < name.length(); i++) {
                char c = name.charAt(i);

                boolean flag = Character.isLowerCase(oldChar) && Character.isUpperCase(c);

                oldChar = c;

                if (flag) {
                    buffer.append('_');
                }

                buffer.append(
                        Character.toUpperCase(c)
                );
            }

            return buffer.toString();
        }

        return name;
    }

    /**
     * Get default package name from a namespace URI.<br>
     * This function follows the JSR-222 JAXB 2.0 algorithm from https://jaxb.dev.java.net/spec-download.html.<br>
     *
     * Based on simalar function from SDO.
     *
     * @param uriString - a namespace URL or URN
     * @return Java package name String
     */
    private static String getPackageNameFromURI(String uriString) {
        String strToken;
        String prefix;
        int position;
        StringBuffer pkgName = new StringBuffer();

        if (null == uriString || uriString.equals(EMPTY_STRING)) {
            return DEFAULT_PACKAGE_NAME;
        }

        /**
         * Step 1: (Remove the schema and ":" part)
         *
         * An XML namespace is represented by a URI. Since XML Namespace will be
         * mapped to a Java package, it is necessary to specify a default mapping from a
         * URI to a Java package name. The URI format is described in [RFC2396].
         * The following steps describe how to map a URI to a Java package name. The
         * example URI, http://example.org/go/file.xsd, is used to illustrate each step.
         *
         * 1. Remove the scheme and ":" part from the beginning of the URI, if present.
         *
         * Since there is no formal syntax to identify the optional URI scheme, restrict
         * the schemes to be removed to case insensitive checks for schemes
         * "http" and "urn".
         *
         * //example.org/go/file.xsd
         */

        // Remove only urn: and http: schemes - retain ftp, file, gopher, mail, news, telnet

        URI uri;
        String schemePrefix;

        // Save error state so that we can emit a warning after the URI has been processed
        boolean invalidOriginalFormat = false;

        // Save whether we are a supported urn or http scheme
        boolean invalidScheme = false;

        try {
            // Creating a URI object and catching a syntax exception may be a performance hit
            uri = new URI(uriString);

            schemePrefix = uri.getScheme();

            // Remove http or urn schemes for valid URI's
            if (null != schemePrefix) {
                if ((schemePrefix.equalsIgnoreCase("http") || schemePrefix.equalsIgnoreCase("urn"))) {
                    uriString = uri.getSchemeSpecificPart();
                }
                else {
                    invalidScheme = true;
                }
            }
        }
        catch (NullPointerException npe) {
            return DEFAULT_PACKAGE_NAME;
        }
        catch (URISyntaxException use) {
            // Warn that the URI is invalid, but process the string into a valid package anyway
            invalidOriginalFormat = true;

            // Remove http or urn schemes for invalid URI's
            if (uriString.length() > 4) {
                prefix = uriString.substring(0, 4);

                if (prefix.equalsIgnoreCase("urn:")) {
                    uriString = uriString.substring(4);
                }
                else {
                    prefix = uriString.substring(0, 5);
                    if (prefix.equalsIgnoreCase("http:")) {
                        uriString = uriString.substring(5);
                    }
                }
            }
        }
        finally {
            /**
             * Step 2: remove trailing file type, one of .?? or .??? or .html.
             * //example.org/go/file
             * Note: The trailing host fragment will be removed for non http|urn schemes such as file:.
             */

            int potentialPathSepIndex = uriString.lastIndexOf('/'); // Don't handle ? param separator on purpose
            int potentialHostSepIndex = uriString.indexOf('/');
            int potentialFileExtIndex = uriString.lastIndexOf('.');

            /**
             * When to remove the last .ext or trailing host fragment.
             * Valid scheme	|  has file ext	= remove/keep last {.[^.]+} fragment
             * 0 | 0 Remove host prefix			ie: file://site.com -> file.site
             * 0 | 1 Remove file ext				ie: file://site.com/file.xsd -> file.com.site
             * 1 | 0 Don't remove host prefix	ie: urn://site.com -> com.site
             * 1 | 1 Remove file ext				ie: urn://site.com/file.xsd -> com.site
             */

            // Don't Remove trailing host fragment for http|urn schemes
            if ((invalidScheme && potentialFileExtIndex != -1) || //
                    ((potentialFileExtIndex != -1 && potentialPathSepIndex != -1 && //
                            potentialHostSepIndex != -1 && (potentialPathSepIndex - potentialHostSepIndex) > 1))) { // -1's are handled
                String extension = uriString.substring(potentialFileExtIndex);
                if (extension.length() == 3 || extension.length() == 4 || extension.equalsIgnoreCase(".html")) {
                    uriString = uriString.substring(0, potentialFileExtIndex);
                }
            }

            /**
             * Step 3: (split string into word list) 3. Parse the remaining
             * String into a list of strings using / and : as separators
             * Treat consecutive separators as a single separator.
             * {"example.org", "go", "file" }
             */

            StringTokenizer aTokenizer = new StringTokenizer(uriString, "/:");

            int length = aTokenizer.countTokens();
            if (length == 0) {
                return DEFAULT_PACKAGE_NAME;
            }

            /**
             * Step 4: (unescape each escape sequence octet) 4. For each string
             * in the list produced by previous step, unescape each escape
             * sequence octet. {"example.org", "go", "file" } Generating a
             * Java package name 4/19/06 JAXB 2.0 - Final Release 341
             */

            ArrayList<String> strings = new ArrayList<String>(length);

            while (aTokenizer.hasMoreTokens()) {
                strToken = aTokenizer.nextToken();
                strings.add(decodeUriHexadecimalEscapeSequence(strToken));
            }

            /**
             * Step 5: replace [-] with [.] if the scheme is a URN 5. If the
             * scheme is a urn, replace all dashes, -, occurring in the
             * first component with [.].2
             */

            /**
             * Step 6: Apply algorithm described in Section 7.7 Unique Package
             * Names in [JLS] to derive a unique package name from the
             * potential internet domain name contained within the first
             * component. The internet domain name is reversed, component by
             * component. Note that a leading www. is not considered part of
             * an internet domain name and must be dropped. If the first
             * component does not contain either one of the top-level domain
             * names, for example, com, gov, net, org, edu, or one of the
             * English two-letter codes identifying countries as specified in
             * ISO Standard 3166, 1981, this step must be skipped. {org,
             * example, go, file}
             */

            strToken = strings.remove(0).toLowerCase();

            // Reuse the Tokenizer - tokenize on package separator
            aTokenizer = new StringTokenizer(strToken, ".");

            // Check for URI's that are composed only of metacharacter package separators
            if (aTokenizer.countTokens() < 1) {
                return DEFAULT_PACKAGE_NAME;
            }
            else {
                while (aTokenizer.hasMoreTokens()) {
                    strToken = aTokenizer.nextToken();
                    if (!strToken.equals("www")) {
                        strings.add(0, strToken);
                    }
                }
            }

            /**
             * Step 7: (convert each string to be all lower case) 7. For each
             * string in the list, convert each string to be all lower case.
             * {org, example, go, file }
             */
            position = 0;

            for (String aString : strings) {
                strings.set(position++, aString.toLowerCase());
            }

            /**
             * Step 8: (convert each string to a valid identifier) 8. For each
             * string remaining, the following conventions are adopted from
             * [JLS] Section 7.7, Unique Package Names. Follow step 8a-c
             * below.
             */
            position = 0;

            for (String aString : strings) {
                StringBuffer buffer = new StringBuffer();

                /**
                 * Step 8a: If the string component contains a hyphen, or any other
                 * special character not allowed in an identifier, convert it
                 * into an underscore.
                 */
                for (int j = 0; j < aString.length(); j++) {
                    char charToken = aString.charAt(j);
                    if (Character.isJavaIdentifierPart(charToken)) {
                        buffer.append(charToken);
                    }
                    else {
                        buffer.append('_');
                    }
                }

                /**
                 * Step 8b:
                 * From the Java Language Specification section 7.7 b. If any of
                 * the resulting package name components are keywords then
                 * append underscore to them.
                 * We are not performing this step here - and are allowing all java reserved keywords to pass
                 * See the enum com.sun.tools.javac.parser.Token for a list of keywords
                 */

                /**
                 * Step 8c: If any of the resulting package name components start with
                 * a digit, or any other character that is not allowed as an
                 * initial character of an identifier, have an underscore
                 * prefixed to the component. {org, example, go, file }
                 */
                if (!Character.isJavaIdentifierStart(buffer.charAt(0))) {
                    buffer.insert(0, '_');
                }
                if (position++ != 0) {
                    buffer.insert(0, '.');
                }

                pkgName.append(buffer.toString());
            }
        }

        return pkgName.toString();
    }

    /**
     * Decode Hexadecimal "%hh" escape sequences in a URI.
     * All escape codes must be valid 2 digit sequences.
     *
     * @param uri - URI component
     * @return URI component with escape sequence decoded into a
     */
    private static String decodeUriHexadecimalEscapeSequence(String uri) {
        // This function is used by the Java Package Name generation algorithm that implements JAXB 2.0 D.5.1
        StringBuffer sb = new StringBuffer(uri.length());

        for (int index = 0; index < uri.length(); index++) {
            char c = uri.charAt(index);

            // Escape sequence found - get the hex value and convert
            if (c == '%') {
                if (((index + 2) < uri.length()) && //
                        HEXADECIMAL_DIGITS.indexOf(uri.charAt(index + 1)) >= 0 &&//
                        HEXADECIMAL_DIGITS.indexOf(uri.charAt(index + 2)) >= 0) {

                    // Look ahead 2 digits
                    String g = uri.substring(index + 1, index + 3);

                    // Convert base 16 to base 10 to char and append
                    sb.append((char) Integer.parseInt(g, 16));

                    /**
                     * Increase the index by 2 - so we skip the 2 digit hex code after the %
                     * See JAXB 2.0 spec p.348 section D.5.1.4
                     * "For each string in the list produced by step 3.  Unescape each escape sequence octet.
                     * IE: North%20America should be "North America" and later in step 8
                     * "north_america" not "north_20america"
                     */
                    index += 2;
                }
                else {
                    sb.append(c);
                }
            }
            else {
                sb.append(c);
            }
        }

        return (sb.toString());
    }

    /*
     *
     */

    public static String getDefaultValueAsString(UClassifier classifier) {
        Class<?> instanceClass = null;
        try {
            instanceClass = classifier.getInstanceClass();
        }
        catch (Exception e) {
            // Continue with no instanceClass.
        }

        String defaultValueLiteral = DEFAULT_LITERAL_OBJECT;

        if (instanceClass != null && instanceClass.isPrimitive()) {
            if (instanceClass == Boolean.TYPE) {
                defaultValueLiteral = DEFAULT_LITERAL_BOOLEAN;
            }
            else if (instanceClass == Integer.TYPE) {
                defaultValueLiteral = DEFAULT_LITERAL_INTEGER;
            }
            else if (instanceClass == Float.TYPE) {
                defaultValueLiteral = DEFAULT_LITERAL_FLOAT;
            }
            else if (instanceClass == Double.TYPE) {
                defaultValueLiteral = DEFAULT_LITERAL_DOUBLE;
            }
            else if (instanceClass == Long.TYPE) {
                defaultValueLiteral = DEFAULT_LITERAL_LONG;
            }
            else if (instanceClass == Short.TYPE) {
                defaultValueLiteral = DEFAULT_LITERAL_SHORT;
            }
            else if (instanceClass == Byte.TYPE) {
                defaultValueLiteral = DEFAULT_LITERAL_BYTE;
            }
            else if (instanceClass == Character.TYPE) {
                defaultValueLiteral = DEFAULT_LITERAL_CHARACTER;
            }
        }

        return defaultValueLiteral;
    }

    /*
     *
     */

    public static String getAccessorPrefix(UAttribute attribute) {
        String accessorPrefix;

        if (attribute.getType() == UModel.UBOOLEAN ||
                attribute.getType() == UModel.UBOOLEAN_OBJECT) {
            accessorPrefix = "is";
        }
        else {
            accessorPrefix = "get";
        }

        return accessorPrefix;
    }

    /*
     *
     */

    private static String resolveInstanceTypeName(UClassifier classifier) {
        String s = classifier.getInstanceTypeName();

        // Если не удалось получить из свойств объекта, то попытаться вывести, какое оно могло бы быть.
        // В случае, когда генерация выполн¤ется по мета-объектам, созданным вручную (не генератором в
        // коде инициализации пакета), имя всегда будет отсутствовать.
        if (s == null && (classifier instanceof UEnumeration || classifier instanceof UClass)) {
            String packageName = getPackageNameFromURI(
                    classifier.getPackage().getNamespaceURI()
            );
            String objectName = classifier.getName();

            s = packageName + "." + objectName;
        }

        return s;
    }

    public static String getInstanceTypeName(UClassifier classifier, JavaImportBuilder importBuilder) {
        CompositeName compositeName = CompositeName.build(
                resolveInstanceTypeName(
                        classifier
                )
        );

        importBuilder.addPackage(
                compositeName.namespace
        );

        return compositeName.name;
    }

    public static String getInstanceTypeName(UAttribute attribute, JavaImportBuilder importBuilder) {
        return getInstanceTypeName(
                attribute.getType(), importBuilder
        );
    }

    public static String getInstanceTypeName(UReference reference, JavaImportBuilder importBuilder) {
        String result = getInstanceTypeName(
                reference.getType(), importBuilder
        );

        if (reference.isMany()) {
            importBuilder.addPackage(
                    "ru.myrtle.umf.util"
            );
            result = "UList<" + result + ">";
        }

        return result;
    }

    public static String getInstanceTypeName(UOperation operation, JavaImportBuilder importBuilder) {
        String result = getInstanceTypeName(
                operation.getType(), importBuilder
        );

        if (operation.isMany()) {
            importBuilder.addPackage(
                    "ru.myrtle.umf.util"
            );
            result = "UList<" + result + ">";
        }

        return result;
    }

    public static String getInstanceTypeName(UParameter parameter, JavaImportBuilder importBuilder) {
        String result = getInstanceTypeName(
                parameter.getType(), importBuilder
        );

        if (parameter.isMany()) {
            importBuilder.addPackage(
                    "ru.myrtle.umf.util"
            );
            result = "UList<" + result + ">";
        }

        if (parameter.isVariableLength()) {
            result += "...";
        }

        return result;
    }

    /*
     *
     */

    public static String createParameterTypeNameList(UOperation operation, JavaImportBuilder importBuilder) {
        StringBuilder buffer = new StringBuilder();

        for (Iterator<UParameter> it = operation.getParameters().iterator(); it.hasNext();) {
            UParameter parameter = it.next();

            buffer.append(
                    getInstanceTypeName(
                            parameter, importBuilder
                    )
            );
            buffer.append(" ");
            buffer.append(
                    parameter.getName()
            );

            if (it.hasNext()) {
                buffer.append(", ");
            }

            importBuilder.addPackage(
                    getObjectClassPackage(
                            parameter.getType()
                    )
            );
        }

        return buffer.toString();
    }

    public static String createParameterNameList(UOperation operation) {
        StringBuilder buffer = new StringBuilder();

        for (Iterator<UParameter> it = operation.getParameters().iterator(); it.hasNext();) {
            UParameter parameter = it.next();

            buffer.append(
                    parameter.getName()
            );

            if (it.hasNext()) {
                buffer.append(", ");
            }
        }

        return buffer.toString();
    }

    public static String createParameterTypeNameListForDelegating(UClass klass, UOperation operation, JavaImportBuilder importBuilder) {
        StringBuilder buffer = new StringBuilder();

        buffer.append(
                getInstanceTypeName(
                        klass, importBuilder
                )
        );
        buffer.append(" ");
        buffer.append("self");

        if (operation.getParameters().size() > 0) {
            buffer.append(", ");

            for (Iterator<UParameter> it = operation.getParameters().iterator(); it.hasNext();) {
                UParameter parameter = it.next();

                buffer.append(
                        getInstanceTypeName(
                                parameter, importBuilder
                        )
                );
                buffer.append(" ");
                buffer.append(
                        parameter.getName()
                );

                if (it.hasNext()) {
                    buffer.append(", ");
                }

                importBuilder.addObjectPackage(
                        getObjectClassPackage(
                                parameter.getType()
                        )
                );
            }
        }

        return buffer.toString();
    }

    public static String createParameterNameListForDelegating(String klassVarName, UOperation operation) {
        StringBuilder buffer = new StringBuilder();

        buffer.append(klassVarName);

        if (operation.getParameters().size() > 0) {
            buffer.append(", ");

            for (Iterator<UParameter> it = operation.getParameters().iterator(); it.hasNext();) {
                UParameter parameter = it.next();

                buffer.append(
                        parameter.getName()
                );

                if (it.hasNext()) {
                    buffer.append(", ");
                }
            }
        }

        return buffer.toString();
    }

    public static String createExceptionList(UOperation operation, JavaImportBuilder importBuilder) {
        StringBuilder buffer = new StringBuilder();

        for (Iterator<UClass> it = operation.getExceptions().iterator(); it.hasNext();) {
            UClass exception = it.next();

            buffer.append(
                    exception.getName()
            );

            if (it.hasNext()) {
                buffer.append(", ");
            }

            importBuilder.addObjectPackage(
                    getObjectClassPackage(
                            exception
                    )
            );
        }

        return buffer.toString();
    }

    public static String createOperationSignature(
            UOperation operation,
            JavaImportBuilder importBuilder,
            boolean isAbstract,
            boolean isFinal,
            String suffix,
            String modifier) {

        StringBuffer out = new StringBuffer();

        out.append(modifier);
        out.append(" ");

        if (isFinal) {
            out.append("final ");
        }
        else if (isAbstract) {
            out.append("abstract ");
        }

        out.append(
                getInstanceTypeName(
                        operation, importBuilder
                )
        );
        out.append(" ");

        out.append(operation.getName());

        if (suffix != null && suffix.length() > 0) {
            out.append(suffix);
        }

        out.append("(");
        out.append(
                createParameterTypeNameList(
                        operation, importBuilder
                )
        );
        out.append(")");

        String exceptionSig = createExceptionList(operation, importBuilder);

        if (exceptionSig.length() > 0) {
            out.append(" throws ");
            out.append(exceptionSig);
        }

        return out.toString();
    }

    public static String createOperationSignatureForDelegating(
            UClass klass,
            UOperation operation,
            JavaImportBuilder importBuilder,
            boolean isAbstract,
            boolean isFinal,
            String suffix,
            String modifier) {

        StringBuffer out = new StringBuffer();

        out.append(modifier);
        out.append(" ");

        if (isFinal) {
            out.append("final ");
        }
        else if (isAbstract) {
            out.append("abstract ");
        }

        out.append(
                getInstanceTypeName(
                        operation, importBuilder
                )
        );
        out.append(" ");

        out.append(operation.getName());

        if (suffix != null && suffix.length() > 0) {
            out.append(suffix);
        }

        out.append("(");
        out.append(
                createParameterTypeNameListForDelegating(
                        klass, operation, importBuilder
                )
        );
        out.append(")");

        String exceptionSig = createExceptionList(operation, importBuilder);

        if (exceptionSig.length() > 0) {
            out.append(" throws ");
            out.append(exceptionSig);
        }

        return out.toString();
    }

    /*
     *
     */

    public static String createConversionExpressionForType(UClassifier type, String varType, String varName) {
        if (type == UModel.UCHARACTER) {
            return String.format(CNV_CHARACTER, varName);
        }
        else if (type == UModel.UBOOLEAN) {
            return String.format(CNV_BOOLEAN, varName);
        }
        else if (type == UModel.UBYTE) {
            return String.format(CNV_BYTE, varName);
        }
        else if (type == UModel.USHORT) {
            return String.format(CNV_SHORT, varName);
        }
        else if (type == UModel.UINTEGER) {
            return String.format(CNV_INTEGER, varName);
        }
        else if (type == UModel.ULONG) {
            return String.format(CNV_LONG, varName);
        }
        else if (type == UModel.UFLOAT) {
            return String.format(CNV_FLOAT, varName);
        }
        else if (type == UModel.UDOUBLE) {
            return String.format(CNV_DOUBLE, varName);
        }
        else {
            return String.format(CNV_OBJECT, varType, varName);
        }
    }

    public static String createJavaTypeExpressionForType(UClassifier type, JavaImportBuilder importBuilder) {
        if (type == UModel.UCHARACTER) {
            return "Character.TYPE";
        }
        else if (type == UModel.UBOOLEAN) {
            return "Boolean.TYPE";
        }
        else if (type == UModel.UBYTE) {
            return "Byte.TYPE";
        }
        else if (type == UModel.USHORT) {
            return "Short.TYPE";
        }
        else if (type == UModel.UINTEGER) {
            return "Integer.TYPE";
        }
        else if (type == UModel.ULONG) {
            return "Long.TYPE";
        }
        else if (type == UModel.UFLOAT) {
            return "Float.TYPE";
        }
        else if (type == UModel.UDOUBLE) {
            return "Double.TYPE";
        }
        else {
            String typeName = getInstanceTypeName(
                    type, importBuilder
            );
            return typeName + ".class";
        }
    }

    public static String createExpressionForType(UClassifier type) {
        UPackage pkg = type.getPackage();

        return getModelClassName(pkg) + "." + getClassifierLiteral(type);
    }

    public static String createExpressionForFeature(UFeature feature) {
        UPackage pkg = feature.getContainingClass().getPackage();

        return getModelClassName(pkg) + "." + getFeatureLiteral(feature);
    }

    /*
     *
     */

    public static boolean hasNotProvidedOperations(UClass klass) {
        for (UOperation operation : klass.getAllOperations()) {
            if (operation.isProvided()) {
                continue;
            }

            return true;
        }

        return false;
    }

    public static boolean hasDerivedFeatures(UClass klass) {
        for (UFeature feature : klass.getAllFeatures()) {
            if (feature.isDerived()) {
                return true;
            }
        }

        return false;
    }

    /*
     *
     */

    public static String getBaseClassName(UTemplateContext context, JavaImportBuilder importBuilder) {
        CompositeName compositeName = CompositeName.build(
                context.getConfig().getBaseClassName()
        );
        importBuilder.addPackage(
                compositeName.namespace
        );

        return compositeName.name;
    }

    /*
     *
     */

    // Enum

    public static String getEnumerationClassName(UEnumeration enumeration) {
        return enumeration.getName();
    }

    public static String getEnumerationClassPackage(UEnumeration enumeration) {
        return getPackageNameFromURI(
                enumeration.getPackage().getNamespaceURI()
        );
    }

    // Enum delegate

    public static String getEnumerationDelegateClassName(UEnumeration enumeration) {
        return getEnumerationClassName(enumeration) + "Delegate";
    }

    public static String getEnumerationDelegateClassPackage(UEnumeration enumeration) {
        return getPackageNameFromURI(
                enumeration.getPackage().getNamespaceURI()
        ) + SUFFIX_IMPL;
    }

    // Enum delegate impl

    public static String getEnumerationDelegateImplClassName(UEnumerationLiteral enumerationLiteral) {
        return getEnumerationDelegateClassName(
                enumerationLiteral.getEnumeration()
        ) + "_" + enumerationLiteral.getName();
    }

    public static String getEnumerationDelegateImplClassPackage(UEnumerationLiteral enumerationLiteral) {
        return getEnumerationDelegateClassPackage(
                enumerationLiteral.getEnumeration()
        ) + SUFFIX_IMPL;
    }

    // Object

    public static String getObjectClassName(UClassifier uClass) {
        return uClass.getName();
    }

    public static String getObjectClassPackage(UClassifier uClass) {
        return getPackageNameFromURI(
                uClass.getPackage().getNamespaceURI()
        );
    }

    // Object impl

    public static String getObjectImplClassName(UClass uClass) {
        return uClass.getName() + "Impl";
    }

    public static String getObjectImplClassPackage(UClass uClass) {
        return getPackageNameFromURI(
                uClass.getPackage().getNamespaceURI()
        ) + SUFFIX_IMPL;
    }

    // Op

    public static String getObjectOpClassName(UClassifier classifier) {
        return classifier.getName() + "Operations";
    }

    public static String getObjectOpClassPackage(UClassifier classifier) {
        return getPackageNameFromURI(
                classifier.getPackage().getNamespaceURI()
        ) + SUFFIX_IMPL;
    }

    // OpStub

    public static String getObjectOpStubClassName(UClassifier classifier) {
        return getObjectOpClassName(classifier) + "Stub";
    }

    public static String getObjectOpStubClassPackage(UClassifier classifier) {
        return getPackageNameFromURI(
                classifier.getPackage().getNamespaceURI()
        ) + SUFFIX_IMPL;
    }

    // Factory

    public static String getFactoryClassName(UPackage uPackage) {
        return uPackage.getName() + "Factory";
    }

    public static String getFactoryClassPackage(UPackage uPackage) {
        return getPackageNameFromURI(
                uPackage.getNamespaceURI()
        );
    }

    // Model

    public static String getModelClassName(UPackage uPackage) {
        return uPackage.getName();
    }

    public static String getModelClassPackage(UPackage uPackage) {
        return getPackageNameFromURI(
                uPackage.getNamespaceURI()
        );
    }

    // Converter

    public static String getConverterClassName(UDataType dataType) {
        return dataType.getName() + "Converter";
    }

    public static String getConverterClassPackage(UDataType dataType) {
        return getPackageNameFromURI(
                dataType.getPackage().getNamespaceURI()
        ) + SUFFIX_IMPL + SUFFIX_CONVERTER;
    }

    // Converter stub

    public static String getConverterStubClassName(UDataType dataType) {
        return dataType.getName() + "ConverterStub";
    }

    public static String getConverterStubClassPackage(UDataType dataType) {
        return getPackageNameFromURI(
                dataType.getPackage().getNamespaceURI()
        ) + SUFFIX_IMPL + SUFFIX_CONVERTER;
    }

    // Literals

    public static String getClassifierLiteral(UClassifier uClassifier) {
        return toUnderLineFormat(
                uClassifier.getName()
        );
    }

    public static String getEnumerationLiteralLiteral(UEnumerationLiteral enumerationLiteral) {
        String s1 = getClassifierLiteral(
                enumerationLiteral.getEnumeration()
        );
        String s2 = toUnderLineFormat(
                enumerationLiteral.getName()
        );

        return s1 + "__" + s2;
    }

    public static String getFeatureLiteral(UFeature feature) {
        String s1 = getClassifierLiteral(
                feature.getContainingClass()
        );
        String s2 = toUnderLineFormat(
                feature.getName()
        );

        return s1 + "__" + s2;
    }

    public static String getOperationLiteral(UOperation operation) {
        String s1 = getClassifierLiteral(
                operation.getContainingClassifier()
        );
        String s2 = toUnderLineFormat(
                operation.getName()
        );

        return s1 + "__" + s2;
    }

    public static String getOperationParameterLiteral(UParameter parameter) {
        String s1 = getOperationLiteral(
                parameter.getOperation()
        );
        String s2 = toUnderLineFormat(
                parameter.getName()
        );

        return s1 + "__" + s2;
    }

    public static String getDefaultValueLiteral(UAttribute attribute) {
        return toUnderLineFormat(
                attribute.getName()
        ) + "_DEFAULT";
    }
}
